package hasthi.tools;

import static hasthi.common.constants.HasthiConstants.JOIN_MANAGMENT_RING_ACTION;
import hasthi.common.BaseContext;
import hasthi.common.ErrorManager;
import hasthi.common.HasthiException;
import hasthi.common.InvaliedResourceDataException;
import hasthi.common.LogFile;
import hasthi.common.Utils;
import hasthi.common.XmlBeanUtils;
import hasthi.common.HasthiException.FaultCode;
import hasthi.common.constants.HasthiConstants;
import hasthi.common.constants.WSDMConstants.SystemState;
import hasthi.container.MessageContext;
import hasthi.container.SimpleMessageContext;
import hasthi.container.UnicastContianier;
import hasthi.instrument.JavaInstrumentation;
import hasthi.manager.AssignResourcesDocument;
import hasthi.manager.AssignResourcesResponseDocument;
import hasthi.manager.JoinManagementRingDocument;
import hasthi.manager.JoinManagementRingResponseDocument;
import hasthi.manager.ManagedResourceType;
import hasthi.manager.ManagerAddress;
import hasthi.manager.ManagerHeartBeatResponseDocument;
import hasthi.manager.ManagerHeartbeatDocument;
import hasthi.manager.AssignResourcesDocument.AssignResources;
import hasthi.manager.AssignResourcesDocument.AssignResources.ResourceData;
import hasthi.manager.AssignResourcesResponseDocument.AssignResourcesResponse;
import hasthi.manager.JoinManagementRingResponseDocument.JoinManagementRingResponse;
import hasthi.manager.ManagerHeartbeatDocument.ManagerHeartbeat;
import hasthi.metamodel.ChangeBuffer;
import hasthi.metamodel.ChangeEvent;
import hasthi.metamodel.MixedResourceModel;
import hasthi.metamodel.ResourceUtils;
import hasthi.metamodel.ChangeEvent.ChangeType;
import hasthi.metamodel.resource.EditableManagedResource;
import hasthi.metamodel.resource.ManagedResource;
import hasthi.metamodel.resource.ManagedResource.ResourceType;
import hasthi.tools.analyze.DataCollector;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.namespace.QName;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xmlbeans.XmlObject;

public class ManagerSimulater implements Runnable {
    private static Log log = LogFactory.getLog(HasthiConstants.LOGGER_NAME);
    private TestingUtils testingUtils;
    private Map<QName, ManagedResource> map = new ConcurrentHashMap<QName, ManagedResource>();
    private String localhostName;
    private BaseContext baseContext;
    private UnicastContianier unicastContianier;
    private String name;
    private String coordinatorAddress;
    private Map<QName, ManagedResourceType> resourceDiffMap = new HashMap<QName, ManagedResourceType>();
    private ManagerAddress myAddress;
    private float failProb = 1.00f;
    private DataCollector moc;
    private LogFile logfile = LogFile.getLog("hasthi_manager");
    private Pattern pattern = Pattern.compile("odin[0-9]*\\.cs\\.indiana.edu");

    public ManagerSimulater(BaseContext baseContext, UnicastContianier unicastContianier,
            String name, float failProb) throws Exception {
        localhostName = Utils.getLocalHost();
        this.baseContext = baseContext;
        testingUtils = new TestingUtils(baseContext);
        this.unicastContianier = unicastContianier;
        this.name = name;
        testingUtils.setCreateNewResource(true);
        joinRing();
        moc = new DataCollector("moc(mng" + UUID.randomUUID() + ")", logfile,
                DataCollector.WINDOW_SIZE);
        new Thread(this).start();
        this.failProb = failProb;
        log.info("##CONFIG##Using manager heartbeat " + baseContext.getManagerEpochSeconds());
        log.info("##CONFIG##Using failure probabaility " + failProb);

    }

    public void assignResource(AssignResourcesDocument request) throws HasthiException {
        try {
            AssignResources resource = request.getAssignResources();

            ResourceData[] resourceDatalist = resource.getResourceDataArray();
            AssignResourcesResponseDocument responseDocument = AssignResourcesResponseDocument.Factory
                    .newInstance();
            AssignResourcesResponse assignResourcesResponse = responseDocument
                    .addNewAssignResourcesResponse();
            assignResourcesResponse.setManagerAddress(myAddress);
            ManagedResourceType[] resourceSummeries = new ManagedResourceType[resourceDatalist.length];
            int i = 0;
            for (ResourceData resourceData : resourceDatalist) {

                QName resourceName = resourceData.getName();
                ManagedResource managedResource;
                Matcher matcher = pattern.matcher(resourceName.getLocalPart());
                if (matcher.matches() || resourceName.getLocalPart().equals(localhostName)) {
                    managedResource = testingUtils.createRandomResource(resourceName,
                            ResourceType.Host, resourceName, unicastContianier
                                    .getContainierAddress(), localhostName);
                } else {
                    String[] nameparts = resourceName.getLocalPart().split("_");
                    if (nameparts.length < 2) {
                        String resourceType = resourceName.getLocalPart().replaceAll("[0-9]*", "");
                        boolean isPersistant = false;
                        for (String name : TestingUtils.PERSISTANT_SERVIES) {
                            if (name.equals(resourceType)) {
                                isPersistant = true;
                            }
                        }
                        ResourceType category = isPersistant ? ResourceType.Service
                                : ResourceType.TransientService;
                        managedResource = testingUtils.createRandomResource(resourceName, category,
                                new QName(resourceName.getNamespaceURI(), resourceType),
                                resourceData.getManagementEndpoint(), new URL(coordinatorAddress)
                                        .getHost());
                    } else {
                        boolean isPersistant = false;
                        for (String name : TestingUtils.PERSISTANT_SERVIES) {
                            if (name.equals(nameparts[0])) {
                                isPersistant = true;
                            }
                        }
                        ResourceType category = isPersistant ? ResourceType.Service
                                : ResourceType.TransientService;
                        managedResource = testingUtils.createRandomResource(resourceName, category,
                                new QName(resourceName.getNamespaceURI(), nameparts[0]),
                                resourceData.getManagementEndpoint(), nameparts[1]);
                    }
                }
                map.put(resourceName, managedResource);
                resourceSummeries[i] = managedResource.toXml(true);
                i++;
            }
            assignResourcesResponse.setResourceSummeryArray(resourceSummeries);
            MessageContext response = new SimpleMessageContext(responseDocument,
                    coordinatorAddress, HasthiConstants.ASSIGN_RESOURCE_RESPONSE_ACTION);
            unicastContianier.send(response);
        } catch (InvaliedResourceDataException e) {
            throw new HasthiException(e, FaultCode.InvaliedRequest);
        } catch (MalformedURLException e) {
            throw new HasthiException(e, FaultCode.InternalServiceError);
        }

    }

    public void joinRing() throws InterruptedException, HasthiException {
        coordinatorAddress = ManagementRingTools.findCoordinatorFromBootstrapNode(
                unicastContianier, baseContext);
        myAddress = ManagerAddress.Factory.newInstance();
        myAddress.setSoapAddress(unicastContianier.getContainierAddress() + "/" + name);
        myAddress.setP2Paddress("dymmyAddress");

        JoinManagementRingDocument joinManagementRingDocument = JoinManagementRingDocument.Factory
                .newInstance();
        joinManagementRingDocument.addNewJoinManagementRing().setAddress(myAddress);
        MessageContext messageContext = new SimpleMessageContext(joinManagementRingDocument,
                coordinatorAddress, JOIN_MANAGMENT_RING_ACTION);
        JoinManagementRingResponseDocument joinManagementRingResponseDocument = (JoinManagementRingResponseDocument) unicastContianier
                .sendReceive(messageContext);
        JoinManagementRingResponse joinManagementRingResponse = joinManagementRingResponseDocument
                .getJoinManagementRingResponse();
        baseContext.getDlogger().info(
                "Manager Sucessfully Started (" + myAddress.getSoapAddress()
                        + ")|Joining Coordination Ring Suceeded");
    }

    public void run() {
        while (true) {
            try {
                MixedResourceModel mixedResourceModel = testingUtils.getMixedResourceModel();
                Collection<QName> resourcesList = mixedResourceModel.getResources();
                String msg = "Manager (" + this.hashCode() + "/" + true + "/"
                        + resourcesList.size() + "/" + 0 + ") " + new Date().toString() + "("
                        + JavaInstrumentation.getMemoryUsage() + ")";
                log.warn(msg);
                ArrayList<QName> toremoveList = new ArrayList<QName>();
                for (QName name : resourcesList) {
                    ManagedResource resource = mixedResourceModel.getResource(name);
                    testingUtils.performChange4Coordinator((EditableManagedResource) resource,
                            failProb);
                    if (SystemState.CrashedState.toString().equals(resource.getState())) {
                        toremoveList.add(name);
                    }
                }

                long point1 = System.nanoTime();
                ManagerHeartbeatDocument heartbeatDocument = createHeartbeatWithChange(mixedResourceModel);
                MessageContext message = new SimpleMessageContext(heartbeatDocument,
                        coordinatorAddress, HasthiConstants.MANAGER_HEARTBEAT_ACTION);
                ManagerHeartBeatResponseDocument responseDocument = (ManagerHeartBeatResponseDocument) unicastContianier
                        .sendReceive(message);
                long point2 = System.nanoTime();
                moc.addAReading(point2 - point1);
                Thread.sleep(baseContext.getManagerEpochSeconds() * 1000);
                for (QName name : toremoveList) {
                    mixedResourceModel.removeResource(name);
                }
            } catch (Throwable e) {
                e.printStackTrace();
                log.error(e);
            }
        }
    }

    private ManagerHeartbeatDocument createHeartbeatWithChange(MixedResourceModel resourceModel)
            throws HasthiException {
        ManagerHeartbeatDocument document;
        document = ManagerHeartbeatDocument.Factory.newInstance();
        ManagerHeartbeat heartbeat = document.addNewManagerHeartbeat();
        heartbeat.setAddress(myAddress);
        /**
         * We build the heartbeat message using the changes, and only send
         * summerized parameters.
         */
        int changeCount = 0;
        ChangeBuffer changes = resourceModel.getChangeBuffer();
        List<ChangeEvent> changeEvents = changes.reset();
        for (ChangeEvent change : changeEvents) {
            if (ChangeType.Update.equals(change.getChangeType())) {
                String propertyName = change.getPropertyName().getLocalPart();
                if (ResourceUtils.isIncludedInSummerized(propertyName)) {
                    ManagedResourceType resourceType = resourceDiffMap.get(change.getResourceID());
                    if (resourceType == null) {
                        resourceType = ManagedResourceType.Factory.newInstance();
                        resourceDiffMap.put(change.getResourceID(), resourceType);
                    }
                    XmlBeanUtils.addNameValuePair(resourceType, change.getPropertyName(), change
                            .getNewValue());
                    changeCount++;
                }
            }
            // TODO handle other changes types, add/remove. Add is automatically
            // handled. We need to handle removes
        }

        log.info("Sending change for " + changeCount + "/" + resourceModel.getResources().size());
        // Xmlbeans complains if we add properties and children to a element
        // mixed. So we set the properties at the end
        for (QName name : resourceDiffMap.keySet()) {
            ManagedResourceType resourceType = resourceDiffMap.get(name);
            resourceType.setName(name);
            resourceType.setChangesOnly(true);
        }

        heartbeat.setPiggybackUpdateArray(resourceDiffMap.values().toArray(
                new ManagedResourceType[0]));
        resourceDiffMap.clear();
        return document;
    }

    public String getName() {
        return name;
    }

    public TestingUtils getTestingUtils() {
        return testingUtils;
    }

}
